package jmine.tec.persist.jms;

import java.util.Enumeration;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import jmine.tec.persist.jms.dao.JMSMonitoringMessageDAO;
import jmine.tec.persist.jms.domain.JMSMonitoringMessage;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import bancosys.tec.persist.controller.TransactionalController;
import bancosys.tec.utils.date.Timestamp;

/**
 * Classe para tratamento (envio/recebimento) de mensagens de testes.
 * 
 * @author rodrigo.queiroz
 */
public class TestMessageSender {

    public static final String TEST_MESSAGE_PREFIX = "MapsMQTester=";

    public static final Log LOGGER = LogFactory.getLog(TestMessageSender.class);

    private TransactionalController controller;

    private JMSMonitoringMessageDAO dao;

    /**
     * Construtor.
     * 
     * @param controller TransactionalController
     */
    public TestMessageSender(TransactionalController controller) {
        super();
        this.controller = controller;
        this.dao = this.controller.getDAOFactory().getDAOByClass(JMSMonitoringMessageDAO.class);
    }

    /**
     * Envia mensagem de testes para a fila especificada.
     * 
     * @param connectionFactory JMS da Connection Factory
     * @param queue String com JMS da Fila
     */
    public void sendTestMessage(String connectionFactory, String queue) {

        JMSMonitoringMessage message = createAndPersistMessage(connectionFactory, queue);
        QueueConnectionFactory queueConnectionFactory;
        Queue targetQueue;
        try {
            queueConnectionFactory = getQueueConnectionFactory(connectionFactory);
            targetQueue = getQueue(queue);
            message.setSendMqQueue(targetQueue.getQueueName());
        } catch (Throwable e) {
            LOGGER.error("Erro ao buscar queue ou queue connection factory", e);
            message.setErrorMessage("Erro ao buscar queue ou queue connection factory. " + e.getMessage());
            updateMessage(message);
            return;
        }

        QueueConnection queueConnection = null;
        try {
            queueConnection = queueConnectionFactory.createQueueConnection();
            QueueSession queueSession = queueConnection.createQueueSession(false, javax.jms.Session.AUTO_ACKNOWLEDGE);
            QueueSender queueSender = queueSession.createSender(targetQueue);
            TextMessage textMessage = queueSession.createTextMessage();

            textMessage.setText(TEST_MESSAGE_PREFIX + message.getId());
            queueSender.send(textMessage);
            updateMessage(message);
        } catch (Throwable e) {
            LOGGER.error("Erro enviar mensagem de testes", e);
            message.setErrorMessage("Erro enviar mensagem de testes. " + e.getMessage());
            updateMessage(message);
        } finally {
            if (queueConnection != null) {
                try {
                    queueConnection.close();
                } catch (JMSException e) {
                    LOGGER.error("Erro ao tentar fechar conexao", e);
                }
            }
        }
    }

    /**
     * Cria e salva o registro da mensagem de testes.
     * 
     * @param connectionFactory JMS da Connection Factory
     * @param queue String com JMS da Fila
     * @return Registro da mensagem criado.
     */
    private JMSMonitoringMessage createAndPersistMessage(String connectionFactory, String queue) {
        JMSMonitoringMessage message = null;
        try {
            this.controller.startTransaction();
            message = this.dao.createBean();
            message.setSendQueueJndi(queue);
            message.setConnectionFactoryJndi(connectionFactory);
            message.setSendTimestamp(new Timestamp());
            message.getPersister().save();
            this.controller.commit();
        } catch (Throwable e) {
            LOGGER.error("Erro salvar mensagem monitorada. ", e);
            try {
                this.controller.rollback();
            } catch (Throwable e1) {
                LOGGER.error("Erro dar rollback. ", e);
            }
        }
        return message;
    }

    /**
     * Devolve a QueueConnectionFactory definida por <code>connectionfactory</code>.
     * 
     * @param connectionfactory O nome jndi do connection factory.
     * @return a QueueConnectionFactory.
     * @throws NamingException quando o <code>connectionfactory</code> não está definido no contexto jndi.
     */
    private QueueConnectionFactory getQueueConnectionFactory(String connectionfactory) throws NamingException {
        return (QueueConnectionFactory) getJndiContext().lookup(connectionfactory);
    }

    /**
     * Devolve a fila associada ao <code>queueName.</code>
     * 
     * @param queue O nome jndi da fila.
     * @return a fila associada ao <code>queueName.</code>.
     * @throws NamingException quando o <code>queueName.</code> não está definido no contexto jndi.
     */
    private Queue getQueue(String queue) throws NamingException {
        return (Queue) getJndiContext().lookup(queue);
    }

    /**
     * @return the jndiContext
     * @throws NamingException the naming exception
     */
    private Context getJndiContext() throws NamingException {
        return new InitialContext();
    }

    /**
     * Valida se a mensagem recebida é uma mensagem de testes.
     * 
     * @param msg Mensagem recebida
     * @return <code>true</code> caso seja uma mensagem de testes
     */
    public static boolean isTestMessage(Message msg) {
        if (msg instanceof TextMessage) {
            TextMessage txtMsg = (TextMessage) msg;
            try {
                String text = txtMsg.getText();
                return text != null && text.startsWith(TestMessageSender.TEST_MESSAGE_PREFIX);
            } catch (Throwable t) {
                // OK
            }
        }
        return false;
    }

    /**
     * Atualiza o registro de mensagem de testes.
     * 
     * @param message Registro de mensagem de testes.
     */
    private void updateMessage(JMSMonitoringMessage message) {
        try {
            this.controller.startTransaction();
            message.getPersister().save();
            this.controller.commit();
        } catch (Throwable e) {
            LOGGER.error("Erro salvar mensagem monitorada. ", e);
            try {
                this.controller.rollback();
            } catch (Throwable e1) {
                LOGGER.error("Erro dar rollback. ", e);
            }
        }
    }

    /**
     * Registra o recebimento da mensagem de testes.
     * 
     * @param msg Mensagem de testes
     * @param context Contexto do MDB.
     */
    public void monitor(Message msg, String context) {
        if (msg instanceof TextMessage) {
            this.controller.startTransaction();
            try {
                TextMessage txtMsg = (TextMessage) msg;
                String text = txtMsg.getText();
                Long messageId = new Long(text.split(TestMessageSender.TEST_MESSAGE_PREFIX)[1]);
                JMSMonitoringMessage message = this.dao.findByPk(messageId);

                setPutTimestamp(msg, message);
                message.setReceiveTimestamp(new Timestamp());
                StringBuilder sb = new StringBuilder();
                Enumeration propertyNames = msg.getPropertyNames();
                while(propertyNames.hasMoreElements()) {
                    String propName = String.valueOf(propertyNames.nextElement());
                    Object propValue = msg.getObjectProperty(propName);
                    sb.append(propName);
                    sb.append("=");
                    sb.append(String.valueOf(propValue));
                    if(propertyNames.hasMoreElements()) {
                        sb.append(", ");
                    }
                }
                if(msg.getJMSReplyTo() != null) {
                    sb.append("; replyTo=" + msg.getJMSReplyTo());
                }

                message.setReceiveMDB(context + "; params= " + sb.toString());
                message.setReceiveMqQueue(txtMsg.getJMSDestination().toString());
                message.getPersister().save();
                LOGGER.info(text);
                this.controller.commit();
            } catch (Throwable e) {
                LOGGER.error("Erro no recebimento da mensagem monitorada. Msg: " + msg.toString(), e);
                try {
                    this.controller.rollback();
                } catch (Throwable e1) {
                    LOGGER.error("Erro dar rollback. ", e1);
                }
            }
        }
    }

    /**
     * Define na mensagem monitorada a data/hora de postagem da mensagem original na fila.
     * @param message mensagem original
     * @param monitMessage registro de mensagem de monitoramento
     */
    private void setPutTimestamp(Message message, JMSMonitoringMessage monitMessage) {
        try {
            long timestamp = message.getJMSTimestamp();
            monitMessage.setPutTimestamp(new Timestamp(timestamp));
        } catch (JMSException e) {
            String errorMessage = "Nao foi possivel obter o timestamp de postagem (JMSTimestamp/PutTimestamp) da mensagem.";
            LOGGER.warn(errorMessage, e);
            if (monitMessage.getErrorMessage() != null) {
                errorMessage = monitMessage.getErrorMessage() + "\n" + errorMessage + " " + e.getCause();
            }
            monitMessage.setErrorMessage(errorMessage);
        }
    }

}
